package apps.MyJMRI;
import wiiremotej.*;
import wiiremotej.event.*;

import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import jmri.ThrottleManager;
import jmri.ThrottleListener;
import jmri.DccThrottle;
import jmri.jmrit.XmlFile;
import jmri.jmrit.roster.Roster;
import jmri.jmrit.roster.RosterEntry;

import  net.iharder.dnd.*;

public class WiiControl extends WiiRemoteAdapter implements ThrottleListener  {
	static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(WiiControl.class.getName());
	
	static float cruiseSpeed = 0.6f ;
	static float slowSpeed = 0.2f ;
	static String basePath = XmlFile.prefsDir()+"Images/";
	static String endPath = "/image";
	static String extPath=".jpg";

	private RemoteFrame frame;

	private WiiRemote remote;
	private boolean connected = false;
	
	private List<RosterEntry> rosters;
	private List<RosterEntry> runningRosters;
	private ListIterator<RosterEntry> idCur;
	private ListIterator<RosterEntry> runningRostersCur;

	private jmri.jmrit.roster.RosterEntry roster;
	private ThrottleManager tm ;
	private DccThrottle throttle;    
	private Image image;
	private myWorker worker = null;
	private int ONECount;
	private int TWOCount;
	
	private Timer throttleTimer;

	public WiiControl(RemoteFrame frame)
	{
		this.image = null;   
		this.connected = false;
		this.frame = frame;
		rosters = (List<RosterEntry>) Roster.instance().matchingList(null, null, null, null, null, null, null) ;
		runningRosters = new LinkedList<RosterEntry>();		
		tm =jmri.InstanceManager.throttleManagerInstance() ;
		idCur = rosters.listIterator();
		roster = idCur.next();
//		frame.repaint();
		
		new FileDrop(frame, new FileDrop.Listener()
		    { public void filesDropped(File[] files) {

				String fullPath = files[0].getPath();
				log.debug("File dropped "+ fullPath);
				
				ImageIcon icon = new ImageIcon(fullPath);
				image = icon.getImage();		
				if (image.getWidth(null) == -1 )
				{ log.warn("Bad image file: "+fullPath+" Keeping previous one."); return; }
				
				changecurentRosterImage(files[0]);
		} } );
		
		throttleTimer = new Timer(1000, new ActionListener() {
		      public void actionPerformed(ActionEvent e) {
				synchronized(roster)
					{					
						if (throttle == null)
						{
							log.warn("Missing throttle for: "+roster.getId()+" "+roster.getDccLocoAddress());
							ImageIcon icon = new ImageIcon(getClass().getResource("NoThrottle.jpg"));
							image = icon.getImage();
							frameRepaint();
						}
					}
		      } } );
		log.debug("Curent loc is "+roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber());
	}

	protected void frameRepaint() {
		frame.repaint();
	}

	public void setRemote(WiiRemote remote)
	{
		try{
			this.remote = remote;
			remote.setLEDIlluminated(0, true);
			remote.setAccelerometerEnabled(false);
			remote.setIRSensorEnabled(false, WRIREvent.BASIC);
			remote.setSpeakerEnabled(false);			
			} catch (Exception e) {
				log.warn("Got IO Exception: "+e.toString() );
			}
		connected = true;
		changeThrottle();
	}

	public void statusReported(WRStatusEvent evt)
	{
		log.warn("Battery level: " + (double)evt.getBatteryLevel()/2+ "%");
		log.warn("Continuous: " + evt.isContinuousEnabled());
		log.warn("Remote continuous: " + remote.isContinuousEnabled());
	}

	public void disconnected()
	{
		log.error("Remote disconnected... (no retry)");
		this.remote = null;
		stopWorker();
		this.connected = false;
		WiiRemoteJ.findRemotes(frame);
	}
	
	public boolean isConnected() { return connected ; }

	private void loadImage()
	{
		String imagePath = basePath + roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber() + endPath;
		
		int i=0;
		File file = new File(imagePath +i + extPath);
		while ( file.exists() )
		{ i++ ; file = new File(imagePath + i + extPath); }
		i--;
		
		ImageIcon icon = new ImageIcon(imagePath + i + extPath);
		image = icon.getImage();			
		if (image.getWidth(null) == -1 )
			log.error("No such image: "+imagePath + i + extPath);	
		frame.repaint();
	}

	public Image getImage()
	{   
		if ((image==null) || (image.getWidth(null) == -1)) 
		{
			if (remote != null)
			{
				ImageIcon icon = new ImageIcon(getClass().getResource("NoLocImage.jpg"));
				image = icon.getImage();
			}
			else
			{
				ImageIcon icon = new ImageIcon(getClass().getResource("NoControl.jpg"));
				image = icon.getImage();
			}
		}
		return image;
	}

	public String getIDString()
	{
		if (remote == null)
			return("No Wiimote, press 1+2");
		if ((roster != null) && (throttle != null))
			return(roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber() + " / " + throttle.toString() );
		if (roster!=null)
			return(roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber() + " "+roster.getDccLocoAddress()+" throttle not available");
		return("MyJRMI");
	}

	public void notifyThrottleFound(DccThrottle t)
	{  	
		synchronized(roster)
		{
			throttle = t;
			ONECount = 0; TWOCount = 0;  
			loadImage();
			throttleTimer.stop();
		}
		log.debug("Throttle for loc "+roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber());
	}

	private void changeThrottle()
	{   // Synchronized through lock on roster
		// Maybe it's not such a good idea to acquire a new throttle at each new entry
		if ( tm.requestThrottle(roster.getDccLocoAddress().getNumber() , this) == false)
			log.error(roster.getId()+"/ request throttle for "+roster.getDccLocoAddress().getNumber()+" failed");
		throttleTimer.start();
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
			log.warn("Interruped after call for throttle"+roster.getDccLocoAddress().getNumber());
		}
	}

	private void releaseThrottle()
	{  // Synchronized through lock on roster
			stopWorker();
			if (throttle != null)
			{        	
				throttle.dispatch();
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				throttle = null;
				log.debug("Dispatched throttle "+roster.getDccLocoAddress().getNumber());
			}
			else
			{
				tm.cancelThrottleRequest(roster.getDccLocoAddress().getNumber() , this);
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				log.warn("Callback canceled "+roster.getDccLocoAddress().getNumber());
			}
	}

	private void stopWorker()
	{
		if (worker != null)
		{	
			worker.interrupt();
		}
		worker=null;   
	} 

	public void buttonInputReceived(WRButtonEvent evt)
	{
		if (throttle != null)
		{
			if ( (evt.isPressed(WRButtonEvent.ONE) && evt.isPressed(WRButtonEvent.TWO)) )  // E-stop
			{
				throttle.setSpeedSetting( -0.5f ); 
				ONECount=0; TWOCount=0; 
			}
			else
			{
				if (evt.wasPressed(WRButtonEvent.TWO))  // slow - idle
				{
					stopWorker();
					TWOCount++; ONECount = 0;
					switch (TWOCount)
					{
					case 1: throttle.setSpeedSetting(slowSpeed); break;
					case 2: throttle.setSpeedSetting(0); TWOCount = 0; break;	        		
					}
					//log.debug("TWO - idle");
				}   	
				if (evt.wasPressed(WRButtonEvent.ONE)) 	// cruise - max
				{
					stopWorker();
					ONECount++; TWOCount = 0;
					switch (ONECount)
					{
					case 1: throttle.setSpeedSetting(cruiseSpeed); break;
					case 2: throttle.setSpeedSetting(1); ONECount=0; break;
					}
					//log.debug("ONE - cruise");
				}
			}
			if (evt.isPressed(WRButtonEvent.A)) 	// Speed -- loop start
			{
				stopWorker();
				ONECount = 0; TWOCount = 0;
				//log.debug("A Speed -- loop start");
				worker = new myWorker(-1, throttle);        	
			}
			if (evt.wasReleased(WRButtonEvent.A))	// Speed -- loop stop
			{
				stopWorker();        	
				//log.debug("A Speed -- loop stop");
			}       
			if (evt.isPressed(WRButtonEvent.B)) 	// Speed ++ loop start
			{
				stopWorker();
				ONECount = 0; TWOCount = 0;
				//log.debug("B Speed ++ loop start");        	
				worker = new myWorker(1, throttle);
			}
			if (evt.wasReleased(WRButtonEvent.B))	//Speed ++ loop stop
			{
				stopWorker();
				//log.debug("B Speed ++ loop stop");
			}	
			if (evt.wasPressed(WRButtonEvent.PLUS))		// Direction
			{
				//log.debug("Plus - forward");
				ONECount = 0; TWOCount = 0;
				stopWorker();
				throttle.setIsForward( true );
			}
			if (evt.wasPressed(WRButtonEvent.MINUS))
			{
				//log.debug("Minus - reverse");
				ONECount = 0; TWOCount = 0;
				stopWorker();
				throttle.setIsForward( false );
			}        
			if (evt.wasPressed(WRButtonEvent.HOME))     // Lights
			{
				ONECount = 0; TWOCount = 0;
				//log.debug("Home - lights");
				throttle.setF0(! throttle.getF0() ) ;
			}
			// Is it a running locomotive?
			if (throttle.getSpeedSetting()>0)
			{	// mess with the list and the cursor
				if (runningRosters.size()==0)
				{
					runningRosters.add(roster);
					runningRostersCur = runningRosters.listIterator();
				}
				else
					if (!runningRosters.contains(roster))
						runningRostersCur.add(roster);
			}
			else
			{
				if ( (runningRosters.size()>0) && (runningRosters.contains(roster)) )
				{
					runningRosters.remove(roster);
					if (runningRosters.size()>0)
						runningRostersCur = runningRosters.listIterator();
				}
			}
		}

		if (evt.wasPressed(WRButtonEvent.RIGHT)) 	// next roster
			synchronized(roster) {
			if (rosters.size()>1)
			{        	
				releaseThrottle();
				jmri.jmrit.roster.RosterEntry rosterPrev = roster ;
				while ( roster == rosterPrev )
				{
					if (! idCur.hasNext())
						idCur = rosters.listIterator();
					roster = idCur.next() ; 
				}
				//log.debug(roster.getId()+" "+roster.getDccLocoAddress());        	
				changeThrottle();
			}
		}
		if (evt.wasPressed(WRButtonEvent.LEFT)) 	// prev roster
			synchronized(roster) {
			if (rosters.size()>1)
			{
				releaseThrottle();
				jmri.jmrit.roster.RosterEntry rosterPrev = roster ;
				while ( roster == rosterPrev )
				{   
					if (! idCur.hasPrevious())
						idCur = rosters.listIterator(rosters.size());
					roster = idCur.previous() ;    			        		
				}
				//log.debug(roster.getId()+" "+roster.getDccLocoAddress());        	
				changeThrottle();
			}
		}
		if (evt.wasPressed(WRButtonEvent.UP))	// previous running roster
			synchronized(roster) {
			if ( runningRosters.size()>1 )
			{
				releaseThrottle();     		
				jmri.jmrit.roster.RosterEntry rosterPrev = roster ;
				while ( roster == rosterPrev )
				{  
					if (! runningRostersCur.hasPrevious())
						runningRostersCur = runningRosters.listIterator(runningRosters.size());
					roster = runningRostersCur.previous() ;    			        		
				}
				changeThrottle();	        	
			}
			else if ( runningRosters.size()== 1 )
			{
				jmri.jmrit.roster.RosterEntry rosterPrev = runningRosters.get(0) ;
				if (roster != rosterPrev)
				{
					releaseThrottle(); 
					roster = rosterPrev;
					changeThrottle();
				}     	
			}
		} 
		if (evt.wasPressed(WRButtonEvent.DOWN)) 	// next running roster
			synchronized(roster) {       	
			if ( runningRosters.size()>1 )
			{
				releaseThrottle();     		
				jmri.jmrit.roster.RosterEntry rosterPrev = roster ;
				while ( roster == rosterPrev )
				{  
					if (! runningRostersCur.hasNext())
						runningRostersCur = runningRosters.listIterator();
					roster = runningRostersCur.next() ;    			        		
				} 
				changeThrottle();	        	
			}
			else if ( runningRosters.size()== 1 )
			{
				jmri.jmrit.roster.RosterEntry rosterPrev = runningRosters.get(0) ;
				if (roster != rosterPrev)
				{
					releaseThrottle(); 
					roster = rosterPrev;
					changeThrottle();
				}     	
			}
		}
	}
	
	private void changecurentRosterImage(File file)
	{
		if (roster == null)
		{ log.warn("No roster at the moment"); return; }
		
		synchronized(roster)
		{	
			String imagePath = basePath + roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber() + endPath ;		
			int i=0;
			File copy = new File(imagePath +i + extPath);
			while ( copy.exists() ) 
			{ i++ ; copy = new File(imagePath +i + extPath); }
	
			if (i==0)
				copy.getParentFile().mkdirs();			
			
			if ( copy.getParentFile().compareTo( file.getParentFile() ) != 0 )
			{
				try
				{ copyFile(file, copy); }
			    catch (Exception e) 
			    { log.warn("Exception while copying file: "+e.toString()); }
			}
			else
			{
				File prev = file;
				file.renameTo(copy);
				try
				{ prev.createNewFile(); }
			    catch (Exception e) 
			    { log.warn("Exception while creating empty file: "+e.toString()); }
			}
	
			log.debug("File copied to "+ imagePath +i + extPath);
			loadImage();
		}
	}
	
	  private static void copyFile(File in, File out) throws Exception {
		    FileInputStream fis  = new FileInputStream(in);
		    FileOutputStream fos = new FileOutputStream(out);
		    try {
		        byte[] buf = new byte[1024];
		        int i = 0;
		        while ((i = fis.read(buf)) != -1) {
		            fos.write(buf, 0, i);
		        }
		    } 
		    catch (Exception e) {
		        throw e;
		    }
		    finally {
		        if (fis != null) fis.close();
		        if (fos != null) fos.close();
		    }
		  }
}
